Jx.declare('Jx.data.Collection', {

	$extend: 'Jx.Base',

	$actions: [

		'add',

		'update',

		'removeAt',

		'clear',

		'splice',

		'sort'

	],

	keys: null,

	length: 0,

	keyPrefix: 'collection',

	constructor: function(){

		this.keys = [];

		this.$super(arguments);

	},

	onDestroy: function(){

		this.clear();

		this.$super();

	},

	onAdd: function(operation){

		var fn = Array.prototype.splice,

			index = operation.index;

		fn.call(this.keys, index, 0, String(operation.key));

		fn.call(this, index, 0, operation.value);

	},

	onUpdate: function(operation){

		var index = operation.index;

		this.keys[index] = operation.key;

		this[index] = operation.value;

	},

	onRemoveAt: function(operation, index){

		var fn = Array.prototype.splice,

			keys = this.keys;

		Jx.id(operation.key = keys[index], true);

		operation.value = this[index];

		fn.call(this.keys, index, 1);

		fn.call(this, index, 1);

	},

	onSplice: function(operation){

		var from = operation.from,

			count = operation.count,

			items = operation.items,

			fn = Array.prototype.splice,

			spliceArgs = [ from, count ],

			J = Jx,

			removedKeys;

		operation.removedItems = fn.apply(this, spliceArgs.concat(items) );

		//-- generate keys
		for (var i, l = items.length; l--;){

			i = l + 2;

			spliceArgs[ i ] = J.id(this.keyPrefix);

		}

		removedKeys = operation.removedKeys, fn.apply( this.keys, spliceArgs );

		operation.keys = spliceArgs.slice(2);

	},

	onAfterAdd: function(operation){

		var value = operation.value,

			key = operation.key,

			index = operation.index;

		this.onAfterItemAdded( value, key, index );

		this.dispatch('add', value, key, index);

		delete operation.value;

	},

	onAfterUpdate: function(operation){

		this.dispatch('update', operation.value, operation.key, operation.index);

		delete operation.value;

	},

	onAfterRemoveAt: function(operation, index){

		var value = operation.value,

			key = operation.key;

		this.onAfterItemRemoved( value, key, index );

		this.dispatch('remove', value, key, index);

		delete operation.value;

	},

	onAfterItemAdded: function(item, key, index){

	},

	onAfterItemRemoved: function(item, key, index){

		J.id(key, true);

	},

	onAfterSplice: function(operation){

		var addedKeys = operation.keys,

			addedItems = operation.items,

			removedItems = operation.removedItems,

			removedKeys = operation.removedKeys,

			from = operation.from,

			l, item;

		//-- call after item removed
		for(l = removedItems.length; l--;){

			this.onAfterItemRemoved( removedItems[l], removedKeys[l], from + l );

		}

		//-- call after item added
		for(l = addedItems.length; l--;){

			this.onAfterItemRemoved( addedItems[l], addedKeys[l], from + l );

		}

		this.dispatch('splice', addedItems, addedKeys, operation.from, operation.count);

		delete operation.values;

	},

	onClear: function(operation){

		var l = this.length,

			fn = Array.prototype.splice;

		operation.keys = fn.call(this.keys, 0, l);

		operation.values = fn.call(this, 0, l);

	},

	onAfterClear: function(operation){

		var removedItems = operation.values;

		if (removedItems.length){

			this.onAfterItemsRemoved( removedItems, operation.keys, 0 );

		}

		this.dispatch('clear', removedItems);

	},

	allowAdd: function(operation, value, key, index){

		var o = this.createValueReference(key, index);

		operation.key = String(o[0]);

		operation.index = operation.returnValue = index = o[1];

		operation.value = value;

		return index != -1;

	},

	allowUpdate: function(operation, value, key){

		var keys = this.keys,

			index = -1;

		if (Jx.isNumber(key) && key > -1 && key < keys.length){

			index = key;

		} else if (Jx.isString(key)){

			index = this.keyLastIndexOf(key);

		}

		operation.key = String(key);

		operation.index = operation.returnValue = index;

		operation.value = value;

		return index != -1;

	},

	allowRemoveAt: function(operation, index){

		operation.returnValue = undefined;

		return Jx.isNumber(index) &&

			index > -1 &&

			index < this.keys.length;

	},

	allowSplice: function(operation, from, length){

		var allow = Jx.isNumber(from) && Jx.isNumber(length),

			count = this.length,

			keys;

		if (allow){

			operation.from = from = Math.max(from, count);

			operation.count = Math.min(length, count - from);

			operation.items = items = Array.prototype.slice.call(arguments, 3);

		}

		return allow;

	},

	get: function(key){

		return Jx.isNumber(key) ?

			this.itemAt(key)

			:

			this.itemByName(key);

	},

	itemAt: function(index){

		var nil;

		return Jx.isNumber(index) && index > -1 && index < this.keys.length ?

			this[index]

			:

			nil;

	},

	itemByName: function(name, from){

		return this.itemAt(

			this.keyLastIndexOf(name, from)

		);

	},

	keyAt: function(index){

		var keys = this.keys, nil;

		return Jx.isNumber(index) && index > -1 && index < keys.length ?

			keys[index]

			:

			nil;

	},

	set: function(name, value){

		var index = this.keyLastIndexOf(name);

		if (index == -1){

			this.add(value, name);

		} else {

			this.update(value, index);

		}

		return this;

	},

	remove: function(value){

		return this.removeAt(

			this.lastIndexOf(value)

		);

	},

	swap: function(index1, index2){

		var keys = this.keys,

			count = keys.length,

			found = void(0),

			tmp_key, tmp_value;

		if (count > 1 && Jx.isNumber(index1) && Jx.isNumber(index2) &&

			index1 != index2 &&

			index1 >= 0 && index1 < count &&

			index2 >= 0 && index2 < count

		){

			tmp_key = keys[index1];

			tmp_value = this[index1];

			keys[index1] = keys[index2];

			this[index1] = this[index2];

			keys[index2] = tmp_key;

			this[index2] = tmp_value;

			found = [ tmp_value, this[index1] ];

		}

		return found;

	},

	createValueReference: function(key, index){

		var isNumber = Jx.isNumber(key);

		if (!Jx.isNumber(index)){

			index = this.length;

		}

		if (!Jx.isScalar(key) || isNumber){

			if (isNumber){

				index = key;

			}

			key = Jx.id(this.keyPrefix);

		}

		return [key, index];

	},

	keyIndexOf: function(key, from){

		var args = [key];

		if (Jx.isNumber(from)){

			args[1] = from;

		}

		return Array.prototype.indexOf.apply(this.keys, args);

	},

	keyLastIndexOf: function(key, from){

		var args = [key];

		if (Jx.isNumber(from)){

			args[1] = from;

		}

		return Array.prototype.lastIndexOf.apply(this.keys, args);

	},

	indexOf: function(value, from){

		var args = [value];

		if (Jx.isNumber(from)){

			args[1] = from;

		}

		return Array.prototype.indexOf.apply(this, args);

	},

	lastIndexOf: function(value, from){

		var args = [value];

		if (Jx.isNumber(from)){

			args[1] = from;

		}

		return Array.prototype.lastIndexOf.apply(this, args);

	}


});
